var LiveJournal = window.arguments[0];
var dsAccounts = window.arguments[1];
var GetTag = opener.GetTag;
const nsIFilePicker = Components.interfaces.nsIFilePicker;
ljSync = new Object();

function Init() {
	delete ljSync.lastsync;
	delete ljSync.firstitem;
	ljSync.status = document.getElementById("status");
	ljSync.browser = document.getElementById("browser");
	var sftag = GetTag(dsAccounts.currentaccount, "syncfile");
	if (!sftag.hasAttribute("location")) {
		// I dunno why this keeps dumping the cancel button in the middle instead of the end. Makes no sense.
		var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
		var flag = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_IS_STRING + prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_IS_STRING  + prompts.BUTTON_POS_2 * prompts.BUTTON_TITLE_CANCEL;
		create = prompts.confirmEx(window, "No database found", "No database seems to be in memory.", flag, "Create a new one", "Open an old one", null, null, {value:false});

		var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
		switch (create) {
			case 0:
				fp.init(window, "Create a database", nsIFilePicker.modeSave);
				break;
			case 1:
				fp.init(window, "Open a database", nsIFilePicker.modeOpen);
				break;
			case 2:
			default:
				window.close();
				return;
				break;
		}
		fp.appendFilter("Deepest Sender Databases", "*.sqlite");	
		fp.defaultExtension = "sqlite";
		var result = fp.show();
		if ((result == nsIFilePicker.returnOK) || (result == nsIFilePicker.returnReplace)) {
			alert("Please note that these databases are stored unencrypted, and anyone with access\nto the file will be able to read it.");
			ljSync.dbfile = fp.file;
			sftag.setAttribute("location", fp.file.path);
			dsAccounts.saveSettings();
		} else {
			window.close();
			return;
		}
	} else {
		var sflocation = sftag.getAttribute("location");
		ljSync.dbfile = FileIO.open(sflocation);
		if (!ljSync.dbfile.exists()) {
			sftag.removeAttribute("location");
			dsAccounts.saveSettings();
			Init();
			return;
		}
	}
	var storageService = Components.classes["@mozilla.org/storage/service;1"].getService(Components.interfaces.mozIStorageService);
	ljSync.db = storageService.openDatabase(ljSync.dbfile);
	// Set up the database if it hasn't been done already...
	ljSync.db.executeSimpleSQL("CREATE TABLE IF NOT EXISTS `general` (`id` INTEGER PRIMARY KEY  NOT NULL , `username` NVARCHAR NOT NULL , `lastsync` INTEGER)");
	ljSync.db.executeSimpleSQL("CREATE TABLE IF NOT EXISTS `entries` (`itemid` INTEGER PRIMARY KEY  NOT NULL , `subject` NVARCHAR, `event` TEXT NOT NULL , `time` INTEGER NOT NULL , `security` NVARCHAR NOT NULL , `allowmask` INTEGER , `anum` INTEGER NOT NULL , `url` NVARCHAR NOT NULL , `poster` NVARCHAR)");
	ljSync.db.executeSimpleSQL("CREATE TABLE IF NOT EXISTS `properties` (`id` INTEGER PRIMARY KEY  NOT NULL , `itemid` INTEGER NOT NULL , `propid` INTEGER NOT NULL , `propvalue` NVARCHAR NOT NULL )");
	ljSync.db.executeSimpleSQL("CREATE TABLE IF NOT EXISTS `propmap` (`id` INTEGER PRIMARY KEY  NOT NULL , `propname` NVARCHAR NOT NULL )");
	var settings = ljSync.db.createStatement("select username, lastsync from general");
	if (settings.executeStep()) {
		ljSync.username = settings.getString(0);
		if (!settings.getIsNull(1)) {
			ljSync.lastsync = dateToString(unixTimeToDate(settings.getInt32(1)));
		}
		if (ljSync.lastsync) ljSync.firstitem = ljSync.lastsync;
		settings.reset();
	} else {
		settings.reset();
		settings = ljSync.db.createStatement("insert into general (username) values (?1)");
		// Ideally this would just be a null value here but for some reason if I then try to do an SQL update it ignores it.
		settings.bindUTF8StringParameter(0, LiveJournal.username.toLowerCase());
		settings.execute();
	}
	var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
	var fileHandler = ios.getProtocolHandler("file").QueryInterface(Components.interfaces.nsIFileProtocolHandler);
	var fileurl = fileHandler.getURLSpecFromFile(ljSync.dbfile);
	document.getElementById("entrytree").setAttribute("datasources", fileurl);
	var dp = document.getElementById("datepicker"); 
	dp.addEventListener('monthchange', ljSync.monthChanged, true);
	ljSync.makeBoldDays(dp.year, dp.month);
	changeDate(document.getElementById("datepicker"));
	fieldLoseFocus(document.getElementById("postsearch"), "Search all posts");
	// And off we go on our merry syncitems way...
	// All entries returned from syncitems (not getevents) go in here.
	ljSync.entrysync = [];
	// All comments returned by syncitems go in here - this doesn't actually get used for anything as the comment exporter doesn't rely on it.
	ljSync.commentsync = [];
	// Total progress number, to calculate how far through the progressbar is.
	ljSync.totalprogress = 0;
	// This is the number of bytes downloaded so far.
	ljSync.totaldl = 0;
	// Any item IDs that are returned from getevents go in this array - it's used to figure out if any entries have been deleted or not.
	ljSync.downloadeditems = [];
	// This is just me being pedantic with standards. Can't have text outside of a div or p.
	ljSync.browser.contentDocument.body.removeChild(ljSync.browser.contentDocument.body.firstChild);
	ljSync.browser.contentDocument.body.appendChild(ljSync.browser.contentDocument.createElement("div"));
	ljSync.displayEntry();
	ljSync.syncItems();
}

ljSync.monthChanged = function(event) {
	var month = parseInt(document.getAnonymousElementByAttribute(event.target, "anonid", "monthlabeldeck").selectedIndex, 10);
	var year = parseInt(document.getAnonymousElementByAttribute(event.target, "anonid", "yearlabel").getAttribute("value"), 10);
	ljSync.makeBoldDays(year, month);
}

ljSync.syncItems = function() {
	// Build up the sync request and away we go.
	if (ljSync.syncreq) {
		ljSync.status.value = "Downloading index... " + howMuch(ljSync.syncreq.request.getResponseHeader("Content-Length"));
	} else {
		ljSync.status.value = "Downloading index...";
	}
	ljSync.syncreq = new LiveJournal.LJHttpRequest(LiveJournal.posturl);
	ljSync.syncreq.onload = ljSync.syncLoaded;
	var params = new Object();
	params.user = LiveJournal.username.toLowerCase();
	if (ljSync.lastsync) params.lastsync = ljSync.lastsync;
	var body = "mode=syncitems";
	body += LiveJournal.makeParams(params);
	ljSync.syncreq.send(body);
}

ljSync.syncLoaded = function() {
	var progress = document.getElementById("syncloading"); 
	progress.mode = "determined";
	//dump(ljSync.syncreq.request.responseText);
	var result = LiveJournal.responseArray(ljSync.syncreq.request.responseText);
	if (LiveJournal.getResponseData("success", result) == "OK") {
		var synccount = parseInt(LiveJournal.getResponseData("sync_count", result), 10);
		var synctotal = parseInt(LiveJournal.getResponseData("sync_total", result), 10)
		// For some reason I can't get this to hit 100%. No idea why.
		if (!ljSync.grandtotal) ljSync.grandtotal = synctotal;
		ljSync.totalprogress = Math.floor((ljSync.grandtotal - synctotal + synccount) / ljSync.grandtotal * 100);
		progress.value = ljSync.totalprogress;
		for (i = 1; i <= synccount; i++) {
			var sync = [];
			sync.push(LiveJournal.getResponseData("sync_" + i + "_item", result));
			sync.push(LiveJournal.getResponseData("sync_" + i + "_time", result));
			sync.push(LiveJournal.getResponseData("sync_" + i + "_action", result));
			sync.push(false);
			//sync.push(sync[0].split("-")[1]);
			if ((!ljSync.lastsync) || (sync[1] > ljSync.lastsync)) ljSync.lastsync = sync[1];
			if ((!ljSync.firstitem) || (sync[1] < ljSync.firstitem)) ljSync.firstitem = sync[1];			
			if (sync[0][0] == "L") {
				//ljSync.entrysync.push(sync);
				var itemid = parseInt(sync[0].split("-")[1], 10);
				ljSync.entrysync[itemid] = sync;
			} else if (sync[0][0] == "C") {
				ljSync.commentsync.push(sync);
			}
		}
		if (!ljSync.entrysync.length) {
			document.getElementById("status").value = "Nothing to update!";
			progress.value = 100;
			ljSync.loadPropsIntoArray();
			return;
		}
		if (synccount != synctotal) {
			ljSync.syncItems();
		} else {
			progress.value = 100;
			ljSync.lastsync = ljSync.firstitem;
			document.getElementById("syncloading").mode = "undetermined";
			ljSync.loadPropsIntoArray();
			ljSync.getEvents();
		}
	} else {
		var errmsg = LiveJournal.getResponseData("errmsg", result);
		if (errmsg) {
			alert(errmsg);
			//alert(commonbundle.getString("ErrorRequest") + " " + errmsg);
		} else {
			
			//alert(commonbundle.getString("ErrorRequest") + " " + result);
		}
	}
}

ljSync.getEvents = function() {
	if (ljSync.eventsreq) {
		ljSync.status.value = "Getting events... " + howMuch(ljSync.eventsreq.request.getResponseHeader("Content-Length"));
	} else {
		ljSync.status.value = "Getting events...";
	}
	// And now we get the actual events that were specified in the lastsync thing. I think. I hope. The documentation is a bit vague with this part.
	ljSync.eventsreq = new LiveJournal.LJHttpRequest(LiveJournal.posturl);
	ljSync.eventsreq.onload = ljSync.eventsLoaded;
	var params = new Object();
	params.user = LiveJournal.username.toLowerCase();
	params.selecttype = "syncitems";
	var tempdate = stringToDate(ljSync.lastsync);
	tempdate.setSeconds(tempdate.getSeconds() - 1);
	params.lastsync = dateToString(tempdate);
	var body = "mode=getevents";
	body += LiveJournal.makeParams(params);
	ljSync.eventsreq.send(body);
}

ljSync.eventsLoaded = function() {
	var result = LiveJournal.responseArray(ljSync.eventsreq.request.responseText);
	//dump(ljSync.eventsreq.request.responseText);
	if (LiveJournal.getResponseData("success", result) == "OK") {
		ljSync.db.beginTransactionAs(ljSync.db.TRANSACTION_DEFERRED);
		var sql = ljSync.db.createStatement("insert or replace into entries (itemid, subject, event, time, security, allowmask, anum, url, poster) values(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)");
		var evcount = parseInt(LiveJournal.getResponseData("events_count", result), 10);
		for (var i = 1; i <= evcount; i++) {
			var itemid = parseInt(LiveJournal.getResponseData("events_" + i + "_itemid", result), 10);
			if (ljSync.entrysync[itemid]) {
				ljSync.entrysync[itemid][3] = true;
				ljSync.downloadeditems[itemid] = true;
			}
			var subject = LiveJournal.getResponseData("events_" + i + "_subject", result);
			var event = LiveJournal.getResponseData("events_" + i + "_event", result, true);
			var time = dateToUnixTime(stringToDate(LiveJournal.getResponseData("events_" + i + "_eventtime", result)));
			var security = LiveJournal.getResponseData("events_" + i + "_security", result);
			var allowmask = LiveJournal.getResponseData("events_" + i + "_allowmask", result);
			var anum = LiveJournal.getResponseData("events_" + i + "_anum", result);
			var url = LiveJournal.getResponseData("events_" + i + "_url", result);
			var poster = LiveJournal.getResponseData("events_" + i + "_poster", result);
			
			sql.bindInt32Parameter(0, itemid);
			sql.bindUTF8StringParameter(1, subject);
			sql.bindUTF8StringParameter(2, event);
			sql.bindInt32Parameter(3, time);
			sql.bindUTF8StringParameter(4, security);
			sql.bindInt32Parameter(5, allowmask);
			sql.bindInt32Parameter(6, anum);
			sql.bindUTF8StringParameter(7, url);
			sql.bindUTF8StringParameter(8, poster);
			
			sql.execute();
			
		}
		
		var propcount = parseInt(LiveJournal.getResponseData("prop_count", result), 10);
		var propsql = ljSync.db.createStatement("insert or replace into properties (itemid, propid, propvalue) values (?1, ?2, ?3)");
		var newprop = ljSync.db.createStatement("insert into propmap (propname) values (?1)");
		for (var i = 1; i <= propcount; i++) {
			var itemid = parseInt(LiveJournal.getResponseData("prop_" + i + "_itemid", result), 10);
			var propname = LiveJournal.getResponseData("prop_" + i + "_name", result);
			var propval = LiveJournal.getResponseData("prop_" + i + "_value", result);
			
			var propindex = ljSync.propmap.indexOf(propname);  
			if (propindex < 0) {
				newprop.bindUTF8StringParameter(0, propname);
				newprop.execute();
				ljSync.propmap[ljSync.db.lastInsertRowID] = propname;
				propindex = ljSync.db.lastInsertRowID;
			}
			
			propsql.bindInt32Parameter(0, itemid);
			propsql.bindInt32Parameter(1, propindex);
			propsql.bindUTF8StringParameter(2, propval);
			propsql.execute();
			
		}
		
		var newest, oldestitem;
		for (var z in ljSync.entrysync) {
			// If the original syncitems request returns deleted items, it fucks out and goes in an infinite loop.
			//	This gets around it. Note where I delete the oldest item.
			if ((!newest) || (newest < ljSync.entrysync[z][1])) newest = ljSync.entrysync[z][1];
			if (!ljSync.entrysync[z][3]) {
				if ((!oldestitem) || (ljSync.lastsync > ljSync.entrysync[z][1])) {
					ljSync.lastsync = ljSync.entrysync[z][1];
					oldestitem = z;
				}
			}
		}
		if (oldestitem) {
			ljSync.entrysync[oldestitem][3] = true;
		} else {
			ljSync.lastsync = newest;
		}
		// Yes I know it sets everything in general to the one time, but there's only ever one record in there and I couldn't get update to work otherwise. 
		var updatels = ljSync.db.createStatement("update general set lastsync = ?1");
		var unixtime = dateToUnixTime(stringToDate(ljSync.lastsync));
		updatels.bindInt32Parameter(0, unixtime);
		//updatels.bindUTF8StringParameter(1, ljSync.username);
		updatels.execute();
		ljSync.db.commitTransaction();
		var dp = document.getElementById("datepicker");
		ljSync.makeBoldDays(dp.year, dp.month);
		
		if (oldestitem) {
			ljSync.getEvents();
		} else  {
			// Delete deleted items from DB...
			// This bit filters out all downloaded entries, so that ljSync.downloadeditems only contain entries that were never
			//	returned from getevents (ie. they were deleted since the last time the user synced).
			for (var z in ljSync.entrysync) {
				if (ljSync.downloadeditems[z]) delete ljSync.downloadeditems[z];
			}
			ljSync.db.beginTransactionAs(ljSync.db.TRANSACTION_DEFERRED);
			var delsql = ljSync.db.createStatement("delete from entries where itemid = ?1");
			for (var z in ljSync.downloadeditems) {
				delsql.bindInt32Parameter(0, ljSync.downloadeditems[z]);
				delsql.execute();
				dump("deleted item " + ljSync.downloadeditems[z] + "\n");
			}
			ljSync.db.commitTransaction();
			ljSync.status.value = "Finished!";
			document.getElementById("syncloading").mode = "determined";
		}
	} else {
		var errmsg = LiveJournal.getResponseData("errmsg", result);
		if (errmsg) {
			alert(errmsg);
			//alert(commonbundle.getString("ErrorRequest") + " " + errmsg);
		} else {
			//alert(commonbundle.getString("ErrorRequest") + " " + result);
		}
	}
}

ljSync.loadPropsIntoArray = function() {
	ljSync.propmap = [];
	var sql = ljSync.db.createStatement("select id, propname from propmap");
	while (sql.executeStep()) {
		var propid = sql.getInt32(0);
		var propname = sql.getUTF8String(1);
		ljSync.propmap[propid] = propname;
	}
	sql.reset();
}

ljSync.displayEntry = function(itemid) {
	if (!itemid) {
		var sql = ljSync.db.createStatement("select itemid, subject, event, time, url from entries order by time desc limit 1");
	} else {
		var sql = ljSync.db.createStatement("select itemid, subject, event, time, url from entries where itemid = ?1");
		sql.bindInt32Parameter(0, itemid);
	}
	if (sql.executeStep()) {
		var itemid = sql.getInt32(0);
		var title = sql.getUTF8String(1);
		var content = sql.getUTF8String(2);
		var time = new Date(sql.getInt32(3) * 1000);
		var url = sql.getUTF8String(4);
		ljSync.posturl = url;
		sql.reset();
		// We already have an array full of property names mapped to IDs... but this is just too convenient...
		var props = ljSync.db.createStatement("select propmap.propname, properties.propvalue from properties inner join propmap on properties.propid = propmap.id where properties.itemid = ?1");
		props.bindInt32Parameter(0, itemid);
		var propsarray = [];
		while (props.executeStep()) {
			var propname = props.getUTF8String(0);
			var propval = props.getUTF8String(1);
			propsarray[propname] = propval;
			
		}
		props.reset();
		content = content.replace(/\n/gi, "<br />");
		var dateformatter = Components.classes["@mozilla.org/intl/scriptabledateformat;1"].getService(Components.interfaces.nsIScriptableDateFormat);
		var prettydate = dateformatter.FormatDateTime("", dateformatter.dateFormatLong, dateformatter.timeFormatNoSeconds,
			time.getFullYear(), time.getMonth() + 1, time.getDate(), time.getHours(), time.getMinutes(), 0); 
		document.getElementById("date").value = "Posted: " + prettydate;
		var titlelabel = document.getElementById("title");
		title ? titlelabel.value = title : titlelabel.value = "[no subject]";
		var music = document.getElementById("music");
		var mood = document.getElementById("mood");
		propsarray["current_music"] ? music.value = "Current Music: " + propsarray["current_music"] : music.value = "Current Music: [none]";
		if (propsarray["current_mood"]) {
			var moodtext = propsarray["current_mood"];
		} else if (propsarray["current_moodid"]) {
			var moodtext = "[unidentified]";
			for (var z in LiveJournal.moods) {
				if (LiveJournal.moods[z].id == propsarray["current_moodid"]) {
					moodtext = LiveJournal.moods[z].mood;
					break;
				}
			}
		} else {
			var moodtext = "[none]";
		}
		mood.value = "Current Mood: " + moodtext;
		ljSync.browser.contentDocument.body.lastChild.innerHTML = content;
	}
}

ljSync.makeBoldDays = function(year, month) {
	// Makes the days with posts bolded. This is a bit more complicated than the original function (at least 28 select queries per month), but runs about a thousand times faster.
	// In: The year to look up, the month to look up. Each day in the month is checked.
	// Out: Nothing.
	var cal = document.getElementById("datepicker");
	var datebox = document.getAnonymousElementByAttribute(cal, "anonid", "datebox");
	var sql = ljSync.db.createStatement("select time from entries where time >= ?1 and time <= ?2");
	var date = new Date();
	date.setFullYear(year, month, 1);
	date.setHours(0,0,0,0);
	sql.bindInt32Parameter(0, date.getTime() / 1000);
	date.setMonth(month + 1);
	date.setSeconds(-1);
	sql.bindInt32Parameter(1, date.getTime() / 1000);
	var timearray = [];
	while (sql.executeStep()) timearray.push(sql.getInt32(0));
	sql.reset();
	for (var i = 0; i < datebox.childNodes.length; i++) {
		var row = datebox.childNodes[i];
		for (var j = 0; j < row.childNodes.length; j++) {
			var box = row.childNodes[j];
			if (box.getAttribute("role") == "gridcell") {
				var day = parseInt(box.getAttribute("value"), 10);
				box.removeAttribute("style");
				box.removeAttribute("tooltiptext");
				if ((day > 0) && (!isNaN(day))) {
					date.setHours(0,0,0,0);
					date.setDate(day);
					var start = date.getTime() / 1000;
					date.setDate(day + 1);
					date.setSeconds(-1);
					var end  = date.getTime() / 1000;
					for (var k = 0; k < timearray.length; k++) {
						if ((timearray[k] >= start) && (timearray[k] <= end)) {
							box.setAttribute("style", "font-weight: bold;");
							break;
						}
					}
				}
			}
		}
	}
}

function setQuery(query) {
	var qtag = document.getElementById("query");
	while (qtag.childNodes.length) qtag.removeChild(qtag.firstChild);
	qtag.appendChild(document.createCDATASection(query));
}

function searchText(text) {
	text = text.replace(/'/g, "''");
	setQuery("select itemid, subject, strftime('%H:%M', datetime(time, 'unixepoch', 'localtime')) as time from entries where event like '%" + text + "%' or subject like '%" + text + "%'");
	var entrytree = document.getElementById("entrytree");
	entrytree.builder.rebuild();
	entrytree.view.selection.select(0);
}

function changeDate(picker) {
	var datestart = document.getElementById("datestart");
	var dateend = document.getElementById("dateend");
	var entrytree = document.getElementById("entrytree");
	var newdate = new Date();
	newdate.setFullYear(picker.year, picker.month, picker.date);
	newdate.setHours(0, 0, 0, 0);
	var ds = newdate.getTime() / 1000;
	newdate.setDate(picker.date + 1);
	newdate.setSeconds(-1);
	var de = newdate.getTime() / 1000;
	
	setQuery("select itemid, subject, strftime('%H:%M', datetime(time, 'unixepoch', 'localtime')) as time from entries where time >= " + ds + " and time <= " + de);
	entrytree.builder.rebuild();
	if (entrytree.view) {
		entrytree.view.selection.clearSelection();
		entrytree.view.selection.select(0);
	}
}

function howMuch(size) {
	ljSync.totaldl += parseInt(size, 10);
	var clean = Math.floor(ljSync.totaldl / 1024);
	if (clean > 1024) return (clean / 1024).toFixed(1) + " MB";
	return clean + " KB";
}

function dateToUnixTime(dateobj) {
	return (dateobj.getTime() - dateobj.getMilliseconds()) / 1000;
}

function unixTimeToDate(unixtime) {
	return new Date(unixtime * 1000);
}

function fieldLoseFocus(obj, defaulttext) {
	if (obj.value.trim().length < 1) {
		addCssClass(obj, "empty");
		obj.value = defaulttext;
	}

}
function fieldGotFocus(obj) {
	if (hasCssClass(obj, "empty")) {
		obj.value = null;
		removeCssClass(obj, "empty");
	}
}
